// src/main/java/com/dbms/storage/BPlusTreePage.java
package com.dbms.storage;

import java.nio.ByteBuffer;

/**
 * B+树节点页面的基类，为内部节点（BPlusTreeInternalPage）和叶子节点（BPlusTreeLeafPage）提供统一的头部格式和基础方法。
 * 核心作用：封装所有B+树节点共有的元数据（如页面类型、大小、父节点ID等），定义节点的基本行为接口。
 *
 * 头部格式（总大小16字节）：
 * ----------------------------------------------------------------
 * | 页面类型(4字节) | 当前大小(4字节) | 最大容量(4字节) | 父节点页ID(4字节) |
 * ----------------------------------------------------------------
 * 页面类型(PageType)：区分当前节点是叶子节点(0)还是内部节点(1)；
 * 当前大小(CurrentSize)：节点中已存储的键值对数量；
 * 最大容量(MaxSize)：节点最多可存储的键值对数量；
 * 父节点页ID(ParentPageId)：当前节点的父节点的页面ID（根节点为INVALID_PAGE_ID）。
 */
public abstract class BPlusTreePage {

    /** 字节缓冲区，用于操作页面的底层字节数据（封装了page的data数组） */
    protected ByteBuffer buffer;
    /** 底层存储页面对象，提供页面ID和脏页标记等基础功能 */
    protected Page page;

    // 头部各字段的偏移量（从页面起始位置开始计算）
    /** 页面类型字段的偏移量（0字节处） */
    protected static final int PAGE_TYPE_OFFSET = 0;
    /** 当前大小字段的偏移量（4字节处） */
    protected static final int SIZE_OFFSET = 4;
    /** 最大容量字段的偏移量（8字节处） */
    protected static final int MAX_SIZE_OFFSET = 8;
    /** 父节点页ID字段的偏移量（12字节处） */
    protected static final int PARENT_PAGE_ID_OFFSET = 12;
    /** 头部总大小（16字节：4+4+4+4） */
    protected static final int HEADER_SIZE = 16;

    /** 无效页ID常量，表示无此页面（如根节点的父节点ID、链表末尾的下一页ID等） */
    public static final int INVALID_PAGE_ID = -1;

    /**
     * 页面类型枚举，区分B+树的两种节点类型
     */
    public enum PageType {
        LEAF_PAGE,    // 叶子节点：存储(key, RID)键值对
        INTERNAL_PAGE // 内部节点：存储(key, 子页ID)映射对
    }

    /**
     * 初始化B+树节点页面的元数据（头部信息）
     * @param page 底层存储页面对象（提供数据存储和页ID）
     * @param maxSize 节点的最大容量（最多可存储的键值对数量）
     * @param pageType 节点类型（叶子节点或内部节点）
     */
    public void init(Page page, int maxSize, PageType pageType) {
        this.page = page; // 绑定底层页面
        this.buffer = ByteBuffer.wrap(page.getData()); // 包装页面数据为ByteBuffer，便于随机读写
        setPageType(pageType); // 设置页面类型
        setSize(0); // 初始大小为0（无数据）
        setMaxSize(maxSize); // 设置最大容量
        setParentPageId(INVALID_PAGE_ID); // 初始父节点ID为无效值（根节点初始化时使用）
    }

    // --- 头部字段访问器（获取和设置头部元数据） ---

    /**
     * 获取当前节点的页面类型（叶子节点或内部节点）
     * @return 页面类型枚举（LEAF_PAGE 或 INTERNAL_PAGE）
     */
    public PageType getPageType() {
        // 从缓冲区读取页面类型标记（0表示叶子节点，1表示内部节点）
        return buffer.getInt(PAGE_TYPE_OFFSET) == 0 ? PageType.LEAF_PAGE : PageType.INTERNAL_PAGE;
    }

    /**
     * 设置当前节点的页面类型（仅初始化时使用）
     * @param pageType 页面类型枚举（LEAF_PAGE 或 INTERNAL_PAGE）
     */
    private void setPageType(PageType pageType) {
        // 写入页面类型标记（0对应叶子节点，1对应内部节点）
        buffer.putInt(PAGE_TYPE_OFFSET, pageType == PageType.LEAF_PAGE ? 0 : 1);
    }

    /**
     * 获取当前节点中已存储的键值对数量
     * @return 当前大小（>=0 且 <= 最大容量）
     */
    public int getSize() {
        return buffer.getInt(SIZE_OFFSET);
    }

    /**
     * 设置当前节点的键值对数量，并标记页面为脏页
     * @param size 新的键值对数量
     */
    public void setSize(int size) {
        buffer.putInt(SIZE_OFFSET, size);
        markDirty(); // 数据修改，标记为脏页
    }

    /**
     * 调整当前节点的键值对数量（增加或减少）
     * @param amount 调整量（正数增加，负数减少）
     */
    public void increaseSize(int amount) {
        setSize(getSize() + amount); // 调用setSize实现，自动标记脏页
    }

    /**
     * 获取当前节点的最大容量（最多可存储的键值对数量）
     * @return 最大容量（初始化时设置，不可动态修改）
     */
    public int getMaxSize() {
        return buffer.getInt(MAX_SIZE_OFFSET);
    }

    /**
     * 设置当前节点的最大容量，并标记页面为脏页（通常仅初始化时使用）
     * @param maxSize 最大键值对数量
     */
    public void setMaxSize(int maxSize) {
        buffer.putInt(MAX_SIZE_OFFSET, maxSize);
        markDirty(); // 数据修改，标记为脏页
    }

    /**
     * 获取当前节点的父节点的页面ID
     * @return 父节点页ID（根节点返回INVALID_PAGE_ID）
     */
    public int getParentPageId() {
        return buffer.getInt(PARENT_PAGE_ID_OFFSET);
    }

    /**
     * 设置当前节点的父节点的页面ID，并标记页面为脏页
     * @param parentPageId 新的父节点页ID
     */
    public void setParentPageId(int parentPageId) {
        buffer.putInt(PARENT_PAGE_ID_OFFSET, parentPageId);
        markDirty(); // 数据修改，标记为脏页
    }

    /**
     * 获取当前节点自身的页面ID
     * @return 页面ID（由底层Page对象提供）
     */
    public int getPageId() {
        return page.getPageId();
    }

    /**
     * 标记当前页面为脏页（数据已修改，需要后续刷写到磁盘）
     */
    protected void markDirty() {
        page.setDirty(true);
    }

    // --- 子类需实现的抽象方法（定义节点的核心行为） ---

    /**
     * 根据键查找其在当前节点中的位置（索引）
     * 作用：1. 键存在时返回其索引；2. 键不存在时返回应插入的位置（保持键有序）
     * @param key 目标查找键
     * @return 键的索引或插入位置
     */
    public abstract int keyIndex(Integer key);

    /**
     * 根据索引获取当前节点中的键
     * @param index 键的索引（需在有效范围内：0 <= index < getSize()）
     * @return 索引对应的键
     */
    public abstract Integer getKey(int index);
}